"use strict";

const fs = require("fs");
const path = require("path");
const inquirer = require("inquirer");
const semver = require("semver");
const fsExtra = require("fs-extra");
const userHome = require("user-home");
const kebabCase = require("kebab-case");
const ejs = require("ejs");
const glob = require("glob");
const Command = require("@zreact-cli/command");
const log = require("@zreact-cli/log");
const Package = require("@zreact-cli/package");
const { spinnerStart, execPromise } = require("@zreact-cli/utils");
const getTemplate = require("./getProjectTemplate");

const TYPE_PROJECT = "project";
const TYPE_COMPONENT = "component";
const TEMPLATE_TYPE_NORMAL = "normal";
const TEMPLATE_TYPE_CUSTOM = "custom";

const WHITE_COMMAND = ["npm", "cnpm", "yarn"];

class InitCommand extends Command {
  init() {
    this.projectName = this._argv[0] || "";
    this.force = !!this._cmd.force;
    log.verbose(this.projectName);
    log.verbose(this.force);
  }
  async exec() {
    try {
      // 1.准备阶段
      const projectInfo = await this.prepare();
      if (projectInfo) {
        this.projectInfo = projectInfo;
        //准备阶段执行完成
        log.verbose("projectInfo", projectInfo);
        // 2.下载模版
        await this.downloadTemplate(projectInfo);
        // 3.安装模版
        await this.installTamplate();
      }
    } catch (error) {
      log.error(error.message);
      if ((process.env.LOG_LEVEL = "verbose")) {
      }
    }
  }

  async downloadTemplate() {
    //1. 通过项目模版api获取项目模版信息
    //1.1 通过egg.js搭建一套后端系统
    //1.2 通过npm存储项目模版
    //1.3 将项目模版信息存储到mongodb数据
    //1.4 通过egg.js获取mongodb中的数据并且通过API返回
    const { template } = this.projectInfo;
    this.templateInfo = this.template.find((item) => item.npmName === template);

    const targetPath = path.resolve(userHome, ".zrcli-cache", "template");
    const storePath = path.resolve(
      userHome,
      ".zrcli-cache",
      "template",
      "node_modules"
    );
    const { npmName, version } = this.templateInfo;
    const templateNpm = new Package({
      targetPath,
      storePath,
      packageName: npmName,
      packageVersion: version,
    });
    if (!(await templateNpm.exists())) {
      const spinner = spinnerStart("正在下载模版");
      try {
        await templateNpm.install();
      } catch (error) {
        throw error;
      } finally {
        spinner.stop(true);
        if (await templateNpm.exists()) {
          log.success("下载模版成功");
          //缓存templateNpm
          this.templateNpm = templateNpm;
        }
      }
    } else {
      const spinner = spinnerStart("正在更新模版");
      try {
        await templateNpm.update();
      } catch (error) {
        throw error;
      } finally {
        spinner.stop(true);
        if (await templateNpm.exists()) {
          log.success("更新模版成功");
          //缓存templateNpm
          this.templateNpm = templateNpm;
        }
      }
    }
  }

  //下载模版
  async installTamplate() {
    //当前模版的信息
    if (this.templateInfo) {
      if (!this.templateInfo.type) {
        this.templateInfo.type = TEMPLATE_TYPE_NORMAL;
      }

      if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
        //标准安装
        await this.installNormalTemplate();
      } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
        //自定义安装
        await this.installCustomTemplate();
      } else {
        throw new Error("项目模版类型无法识别");
      }
    } else {
      throw new Error("项目模版信息不存在");
    }
  }

  //ejs模版渲染
  async ejsRender(option) {
    const dir = process.cwd();
    return new Promise((resolve, reject) => {
      glob(
        "**",
        {
          cwd: dir,
          ignore: option.ignore,
          nodir: true, //不展示文件夹
        },
        (err, files) => {
          if (err) {
            reject(err);
          }
          //获取当前项目下面所有有可能需要ejs展示的文件
          Promise.all(
            files.map((file) => {
              const filePath = path.join(dir, file);

              return new Promise((rs, re) => {
                ejs.renderFile(
                  filePath,
                  this.projectInfo,
                  {},
                  (err, result) => {
                    if (err) re(err);
                    fsExtra.writeFileSync(filePath, result);
                    rs(result);
                  }
                );
              });
            })
          )
            .then(resolve)
            .catch(reject);
        }
      );
    });
  }

  async installNormalTemplate() {
    log.verbose("tamplateInfo", this.templateInfo);
    //安装标准模版
    let spinner = spinnerStart("正在安装模版");
    try {
      //拷贝模版代码到当前目录
      const templatePath = path.resolve(
        this.templateNpm.cacheFilePath,
        "template"
      );
      const targetPath = process.cwd();
      //确保文件夹存在
      fsExtra.ensureDirSync(templatePath);
      fsExtra.ensureDirSync(targetPath);
      // fsExtra.mkdirpSync(this.projectInfo.projectName);

      fsExtra.copySync(
        templatePath,
        targetPath
        // path.resolve(targetPath, this.projectInfo.projectName)
      );
    } catch (error) {
      throw error;
    } finally {
      spinner.stop(true);
      log.success("安装成功");
    }
    const ignore = this.templateInfo.ignore
      ? ["**/node_modules/**", ...this.templateInfo.ignore]
      : ["**/node_modules/**", "**/public/**"];
    await this.ejsRender({ ignore });
    const { installCommand, startCommand } = this.templateInfo;
    let installRes;
    if (installCommand) {
      installRes = await this.execCommand(installCommand, "安装依赖失败");
    }
    //执行成功
    if (installRes === 0) {
      await this.execCommand(startCommand, "启动项目失败");
    } else {
      throw new Error("依赖安装失败");
    }
  }

  async installCustomTemplate() {
    //安装自定义模版
  }

  async execCommand(command, msg) {
    const startCmd = command.split(" ");
    const cmd = startCmd[0];
    let ret;
    if (!this.checkCmd(cmd)) {
      throw new Error("项目启动命令不正确");
    }
    const args = startCmd.slice(1);
    try {
      ret = await execPromise(cmd, args, {
        stdio: "inherit",
        cwd: process.cwd(),
      });
    } catch (error) {
      log.error(error);
    }
    if (ret !== 0) {
      throw new Error(msg);
    }
    return ret;
  }

  //检查命令是否符合白名单
  checkCmd(cmd) {
    return WHITE_COMMAND.includes(cmd);
  }
  //项目信息获取准备（判断文件夹是否为空，是否需要删除等）
  async prepare() {
    //0. 判断项目模版是否存在
    const template = await getTemplate();

    if (!template || template.length === 0) {
      throw new Error("项目模版不存在");
    }
    this.template = template;
    const localPath = process.cwd();
    // 1.判断当前目录是否为空
    const isEmpty = this.isDirEmpty(localPath);

    if (!isEmpty) {
      let ifContinue = false;
      if (!this.force) {
        //没有--force
        ifContinue = (
          await inquirer.prompt({
            type: "confirm",
            name: "ifContinue",
            default: false,
            message: "当前文件夹不为空，是否继续创建项目",
          })
        ).ifContinue;
        if (!ifContinue) return;
      }
      if (ifContinue || this.force) {
        //二次确认是否清空
        const { confirmDel } = await inquirer.prompt({
          type: "confirm",
          name: "confirmDel",
          default: false,
          message: "是否清空文件夹",
        });
        if (confirmDel) {
          // 2.启动强制更新,清空当前目录
          fsExtra.emptyDirSync(localPath);
        }
      }
    }
    // 项目基本信息object
    return this.getProjectInfo();
  }
  //获取项目基本信息
  async getProjectInfo() {
    function isValidateName(v) {
      return /^[a-zA-Z]+[\w-]*[a-zA-Z0-9]$/.test(v);
    }
    let projectInfo = {};
    let isProjectNameValid = false;
    // 1.选择创建项目/组件
    const { type } = await inquirer.prompt({
      type: "list",
      name: "type",
      message: "请选择初始化类型",
      default: TYPE_PROJECT,
      choices: [
        {
          name: "项目",
          value: TYPE_PROJECT,
        },
        {
          name: "组件",
          value: TYPE_COMPONENT,
        },
      ],
    });
    this.configType = type;
    log.verbose(type);
    if (isValidateName(this.projectName)) {
      isProjectNameValid = true;
      projectInfo.projectName = this.projectName;
    }
    const projectNamePrompt = {
      type: "input",
      message: type === TYPE_PROJECT ? "请输入项目名称" : "请输入组件名称",
      name: "projectName",
      default: "",
      validate: function (v) {
        var done = this.async();
        setTimeout(function () {
          if (!isValidateName(v)) {
            done("项目名称不合法,英文字母开头");
            return;
          }
          done(null, true);
        }, 0);
      },
      filter: function (v) {
        return v;
      },
    };
    this.template = this.template.filter((item) => {
      return item.tag.includes(type);
    });
    const project = await inquirer.prompt(
      [
        !isProjectNameValid ? projectNamePrompt : null,
        {
          type: "input",
          message:
            type === TYPE_PROJECT ? "请输入项目版本号" : "请输入组件版本号",
          name: "projectVersion",
          default: "1.0.0",
          validate: function (v) {
            var done = this.async();
            setTimeout(function () {
              if (!semver.valid(v)) {
                done("请输入合法的版本号");
                return;
              }
              done(null, true);
            }, 0);
          },
          filter: function (v) {
            return semver.valid(v) ? semver.valid(v) : v;
          },
        },
        {
          type: "list",
          message: type === TYPE_PROJECT ? "请选择项目模版" : "请选择组件模版",
          name: "template",
          default: "1.0.0",
          choices: this.createTemplateChoices,
        },
      ].filter((item) => !!item)
    );

    // 2.获取项目的基本信息
    if (type === TYPE_PROJECT) {
      projectInfo = {
        ...projectInfo,
        type,
        ...project,
      };
    } else if (type === TYPE_COMPONENT) {
      //组件描述信息
      const desc = await inquirer.prompt({
        type: "input",
        message: "请输入组件描述信息",
        name: "description",
        default: "",
      });

      projectInfo = {
        ...projectInfo,
        type,
        ...project,
        desc,
      };
    }
    //生成package.json里面的className
    if (projectInfo.projectName) {
      projectInfo.className = kebabCase(projectInfo.projectName);
      projectInfo.name = kebabCase(projectInfo.projectName);
    }
    if (projectInfo.projectVersion) {
      projectInfo.version = projectInfo.projectVersion;
    }
    log.verbose("项目信息", projectInfo);
    return projectInfo;
  }

  createTemplateChoices = () => {
    return (this.template || []).map((item) => {
      return {
        name: item.name,
        value: item.npmName,
      };
    });
  };

  isDirEmpty(localPath) {
    let fileList = fs.readdirSync(localPath);
    fileList = fileList.filter((file) => {
      return !file.startsWith(".") && ["node_modules"].indexOf(file) < 0;
    });

    return !(fileList && fileList.length > 0);
  }
}

function init(argv) {
  return new InitCommand(argv);
}

module.exports = init;

module.exports.InitCommand = InitCommand;
